﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Registration;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.Caching;

using NLog.Fluent;

namespace Labthink.PCSoft.UniversalHost4.Utility
{
    public static class Composer<T>
    {
        private static readonly double Timeout;
        private static readonly ObjectCache Cache;

        static Composer()
        {
            Cache = new MemoryCache("MefLocaleComposer");
            Timeout = 10; //缓存10小时
        }

        public static T GetLanguages(string dir, string lang)
        {
            CompositionContainer container;
            var builder = new RegistrationBuilder();
            builder.ForTypesMatching<T>(t => t.Name.EndsWith(lang))
                .Export<T>()
                .SetCreationPolicy(CreationPolicy.NonShared);

            if (Cache.Contains(dir))
            {
                container = (CompositionContainer)Cache[dir];
            }
            else
            {
                var catalog = new AggregateCatalog();
                catalog.Catalogs.Add(new DirectoryCatalog(dir, "uh4.*.dll", builder));
                container = new CompositionContainer(catalog);
                Cache.Add(dir, container, DateTimeOffset.Now.AddHours(Timeout));
            }

            var sw = Stopwatch.StartNew();
            var exported = GetExportedValues(dir, container);
            sw.Stop();
            Log.Trace().Message($"export use {sw.Elapsed}").Write();
            return exported.FirstOrDefault();
        }

        public static IEnumerable<T> GetParts(string dir, CreationPolicy policy= CreationPolicy.NonShared, string type = "uh4.*.dll")
        {
            var builder = new RegistrationBuilder();
            builder.ForTypesDerivedFrom<T>()
                .Export<T>()
                .SetCreationPolicy(CreationPolicy.NonShared);

            var catalog = new AggregateCatalog();
            catalog.Catalogs.Add(new DirectoryCatalog(dir, type, builder));

            var container = new CompositionContainer(catalog);
            var sw = Stopwatch.StartNew();
            IEnumerable<T> exported = GetExportedValues(dir, container);
            sw.Stop();
            Log.Trace().Message($"export use {sw.Elapsed}").Write();
            return exported;
        }

        private static IEnumerable<T> GetExportedValues(string dir, CompositionContainer container)
        {
            try
            {
                var exported = container.GetExportedValues<T>().ToList();
                var count = exported.ToList().Count;
                Log.Trace().Message($"Get {count} from {dir}").Write();
                foreach (T exp in exported)
                {
                    Log.Trace().Message(exp.ToString()).Write();
                }

                return exported;
            }
            catch (CompositionException compositionException)
            {
                var ce = compositionException.UnwrapCompositionException();
                Log.Error().Exception(ce).Write();
                throw;
            }
            catch (ReflectionTypeLoadException ex)
            {
                foreach (Exception loaderException in ex.LoaderExceptions)
                {
                    Log.Error().Exception(loaderException).Write();
                }

                throw;
            }
        }
    }
}
